/*
 * Copyright 2009, Eniro Norge AS ( http://www.eniro.no)
 *
 * This software is the proprietary information of Eniro Norge AS.
 * Use is subject to license terms.
 */
package com.google.code.ei4j;

import java.awt.*;
import java.awt.image.BufferedImage;

/**
 * Operation for scaling images.
 */
class ScaleOperation implements Operation {
    private int targetWidth;
    private int targetHeight;
    private Object hint;
    private boolean higherQuality;
    private boolean keepAspect;

    /**
     * Operation that returns a scaled instance of the
     * provided {@code BufferedImage}.
     *
     * @param targetWidth   the desired width of the scaled instance,
     *                      in pixels
     * @param targetHeight  the desired height of the scaled instance,
     *                      in pixels
     * @param hint          one of the rendering hints that corresponds to
     *                      {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *                      {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *                      {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *                      {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality if true, this method will use a multi-step
     *                      scaling technique that provides higher quality than the usual
     *                      one-step technique (only useful in downscaling cases, where
     *                      {@code targetWidth} or {@code targetHeight} is
     *                      smaller than the original dimensions, and generally only when
     *                      the {@code BILINEAR} hint is specified)
     * @param keepAspect    keep image aspect
     */
    ScaleOperation(int targetWidth, int targetHeight, Object hint, boolean higherQuality, boolean keepAspect) {
        this.targetWidth = targetWidth;
        this.targetHeight = targetHeight;
        this.hint = hint;
        this.higherQuality = higherQuality;
        this.keepAspect = keepAspect;
    }

    /**
     * {@inheritDoc}
     */
    public BufferedImage apply(BufferedImage img) {
        int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB :
                BufferedImage.TYPE_INT_ARGB;
        int originalWidth = img.getWidth();
        int originalHeight = img.getHeight();
        DimensionsCalculator dimensionsCalculator = new DimensionsCalculator(originalWidth, originalHeight).invoke();
        int newWidth = dimensionsCalculator.getNewWidth();
        int newHeight = dimensionsCalculator.getNewHeight();
        int w, h;
        if (useHigherQuality(originalWidth, originalHeight, newWidth, newHeight)) {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = originalWidth;
            h = originalHeight;
        } else {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = newWidth;
            h = newHeight;
        }
        return scale(type, img, w, h, newWidth, newHeight);
    }

    private boolean useHigherQuality(int originalWidth, int originalHeight, int newWidth, int newHeight) {
        return higherQuality && !(newWidth > originalWidth || newHeight > originalHeight);
    }

    private BufferedImage scale(int type, BufferedImage ret, int w, int h, int newWidth, int newHeight) {
        do {
            if (higherQuality && w > newWidth) {
                w /= 2;
                if (w < newWidth) {
                    w = newWidth;
                }
            }

            if (higherQuality && h > newHeight) {
                h /= 2;
                if (h < newHeight) {
                    h = newHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
        } while (w != newWidth || h != newHeight);

        return ret;
    }

    private class DimensionsCalculator {
        private int originalWidth;
        private int originalHeight;
        private int newWidth;
        private int newHeight;

        public DimensionsCalculator(int originalWidth, int originalHeight) {
            this.originalWidth = originalWidth;
            this.originalHeight = originalHeight;
        }

        public int getNewWidth() {
            return newWidth;
        }

        public int getNewHeight() {
            return newHeight;
        }

        public DimensionsCalculator invoke() {
            newWidth = targetWidth;
            newHeight = targetHeight;
            if (keepAspect) {
                double aspectRatio = originalWidth / (double) originalHeight;
                if (aspectRatio < 1.0) {
                    newWidth = (int) (targetWidth * aspectRatio);
                } else {
                    newHeight = (int) (targetHeight / aspectRatio);
                }
            }
            return this;
        }
    }
}
